(function(global) {
	var APP = {},
		$ = global.$;

	APP._regexScript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
	APP._lookup = {
		'Spine.Route' : '/js/vendors/spine-1.0.5/route.js'
	}

	APP._classes = {
		modal : "ui-widget-overlay",
		buttons : {
			button : "ui-button",
			base : "ui-button ui-button-text-only ui-widget ui-state-default",
			text : "ui-button-text"
		},
		states : {
			base : "ui-state-default",
			hover : "ui-state-hover",
			active : "ui-state-active",
			disabled : "ui-state-disabled",
			highlight : "ui-state-highlight",
			error : "ui-state-error",
			unpublished : "unpublished"
		},
		icons : {
			base : "ui-icon",
			help : "ui-icon-help",
			pencil : "ui-icon-pencil",
			locked : "ui-icon-locked",
			plus : "ui-icon-plus",
			transfer : "ui-icon-transfer-e-w",
			triangle_n : "ui-icon-triangle-1-n",
			triangle_s : "ui-icon-triangle-1-s",
			x : "ui-icon-close",
			bullet : "ui-icon-bullet",
			circle : "ui-icon-radio-on",
			check : "ui-icon-check"
		}
	}

	APP.namespace = function(ns, object) {
		var parts = ns.split(/\./g),
			current = APP,
			_i, _len;

		if (parts[0] === 'APP') {
			parts = parts.slice(1);
		}
		for (_i = 0, _len = parts.length - 1; _i < _len; _i++) {
			if (typeof current[parts[_i]] === 'undefined') {
				current[parts[_i]] = {};
			}
			current = current[parts[_i]];
		}
		if (current[parts[_i]]) {
			throw 'App.namespace: namespace already defined for ' + parts[_i] + ' of ' + ns;
			return;
		}
		current[parts[_i]] = object;
	};

	APP.require = function(ns) {
		var parts = ns.split(/\./g),
			current = APP,
			_i, _len;

		if (parts[0] === 'APP') {
			parts = parts.slice(1);
		}
		for (_i = 0, _len = parts.length; _i < _len; _i++) {
			if (typeof current[parts[_i]] === 'undefined') {
				throw 'App.require: namespace not defined for ' + parts[_i] + ' of ' + ns;
				return;
			}
			current = current[parts[_i]];
		}
		return current;
	};

	APP.css = function(src) {
		var parts;
		if (/^[A-Z]{1}[a-z]+\.{1}[A-Z]{1}/.test(src)) {
			parts = src.split('.');
			if (parts[0] === 'Katsu') {
				src = '/css/vendors/katsu/';
				parts.shift();
			} else {
				src = '/css/lib/';
			}
			while (parts.length) {
				src += parts.shift().underscore();
				if (parts.length > 0) {
					src += '/';
				}
			}
			src += '.css';
		}
		this.include(src);
	};

	APP.include = function() {
		//console.log('including', src);
		var args = Array.prototype.slice.call(arguments, 0), src, parts, 
			_lookup = this._lookup,
			_included = this._included || (this._included = {}),
			_includes = this._includes || (this._includes = []);

		while (args.length) {
			src = args.shift();
			if (_lookup[src]) {
				src = _lookup[src];
			} else if (/^[A-Z]{1}[a-z]+\.{1}[A-Z]{1}/.test(src)) {
				parts = src.split('.');
				if (parts[0] === 'Katsu') {
					src = '/js/vendors/katsu/';
					parts.shift();
				} else {
					src = '/js/lib/';
				}
				while (parts.length) {
					src += parts.shift().underscore();
					if (parts.length > 0) {
						src += '/';
					}
				}
				src += '.js';
			}

			if (typeof _included[src] !== 'undefined') {
				continue;
			}
			_included[src] = false;
			_includes.push(src);
		}
		this.load();
	};

	APP.load = function() {
		var _included = this._included,
			_includes = this._includes,
			_callbacks, callback,
			src, self = this;

		if (this._pending) {
			return;
		}

		if (_includes.length) {
			src = _includes.shift();
			this.write(src, function() {
				_included[src] = true;
				self._pending = false;
				self.load();
			});
			return;
		}
		this.attempt('APP.load');
	};

	APP.write = function(src, callback) {
		//console.log('loading', src);
		var self = this,
			doc = global.document,
			node, head;

		function done() {
			this.onload = null;
			this.onreadystatechange = null;
			callback();
		}

		function error() {
			this.onload = null;
			this.onreadystatechange = null;
			throw 'APP.write error loading ' + this.src;
		};
		
		this._pending = true;

		if (/\.css$/.test(src)) {
			node = doc.createElement('link');
			node.type = 'text/css';
			node.rel = 'stylesheet';
			node.href = src;
			callback();
			// no check for CSS having been loaded. may want to fix later.
		} else {
			node = doc.createElement('script');
			node.type = 'text/javascript';
			//node.src = src + '?' + new Date().getTime();
			node.src = src;
			node.onreadystatechange = function() {
				if (/^loaded|complete$/.test(this.readyState)) {
					done.call(this, callback);
				}
			}
			node.onload = function() {
				done.call(this);
			}
			node.onerror = function() {
				error.call(this);
			}
		}

		head = doc.getElementsByTagName('head')[0];
		head.appendChild(node);
	};

	APP.attempt = function(debug) {
		var _included = this._included, _src,
			_includes = this._includes,
			self = this;

		// console.log('trying', debug);
		if (_includes && _includes.length > 0) {
			return;
		}
		for (_src in _included) {
			if (!_included[_src]) {
				return;
			}
		}
		if (!this._ready) {
			$(document).ready(function() {
				self._ready = true;
				self.attempt();
			});
			return;
		}
		this.fire();
	};

	APP.fire = function() {
		var _callbacks = this._callbacks || [],
			callback;

		while (_callbacks.length) {
			callback = _callbacks.shift();
			callback();
		}
		if (Spine.Route && !this._routed) {
			// console.log('routed');
			Spine.Route.setup();
			this._routed = true;
		}
	}

	APP.ready = function(callback) {
		var _callbacks = this._callbacks || (this._callbacks = []);

		_callbacks.push(callback);
		this.attempt('APP.ready');
	};
	APP.uuid = function() {
		function s4() {
			return (((1 + Math.random()) * 0x10000)|0).toString(16).substring(1);
		}
		return (s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4());
	};

/*
	// start UUId module
	APP.Uuid = {
		registry : function(lookup) {
			var uuid, _instances = this.constructor._instances;
			if (!_instances) {
				_instances = (this.constructor._instances = {});
			}
			if (lookup) {
				return _instances[lookup];
			}
			uuid = (this._uuid = APP.uuid());
			this.el.attr('data-uuid', uuid);
			_instances[uuid] = this;
		},
		registered : function(callback) {
			var _instances = this.constructor._instances, id;
			if (!_instances) {
				return;
			}
			if (callback) {
				for (id in _instances) {
					callback(id, _instances[id]);
				}
				return;
			}
			return _instances;
		}
	};
*/
	// start Ajax module for EXTEND of models
	APP.Ajax = {
		ajaxUrl : function(urlOrId, params) {
			var url = this.ajaxes[urlOrId] || urlOrId,
				name, value;

			if (params) {
				if (typeof params === 'string') {
					url += params;
				} else {
					for (name in params) {
						value = params[name];
						if ($.isArray(value)) {
							value = value.join(',');
						}
						url += name + ':' + value + '/'
					}
				}
			}
			url = url.replace(/\/$/, '') + '.json';
			return url;
		},
		ajaxQuery : function(url, params, callback) {
			var url = this.ajaxUrl(url, params);
				self = this;

			$.get(url, function(response) {
				if (!response.success) {
					self.trigger('ajaxError', response);
					throw 'Ajax Module ajaxQuery error';
					return;
				}
				callback(response.data);
			}).error(function() {
				console.log('error calling');	
			});
		},
		ajaxGet : function(url, id, refresh) {
			var exists = this.exists(id), self = this;
			this.trigger('ajaxGetStart');
			if (!exists || refresh) {
				this.ajaxQuery(url, id, function(data) {
					var record = null;
					if (data && !$.isArray(data)) {
						if (exists) {
							record = self.update(data.id, data);
						} else {
							record = self.create(data);
						}
					}
					self.trigger('ajaxGet', record);
				});
				return;
			}
			this.trigger('ajaxGet', this.find(id));
		},
		ajaxRefresh : function(url, params) {
			var self = this;

			this.trigger('ajaxRefreshStart');
			this.ajaxQuery(url, params, function(data) {
				self.refresh(data || [], { clear : true });
			});
		}
	};

	// start Registry module for EXTEND of Controller
	APP.Registry = {
		extended : function() {
			var func = this.registry;
			this.include({
				registry : func
			});
		},
		registry : function(model) {
			var _registry = this._registry || (this._registry = {}),
				Model;

			if (!_registry[model]) {
				_registry[model] = APP.require('Models.' + model);
			}
			Model = _registry[model];
			return Model;
		}
	};

	APP.Panel = {
		_panels : {},
		panelInit : function(defaults) {
			var _panels = this._panels, self = this;

			this.el.find('[data-panel]').each(function(index, element) {
				var $element = $(element), id = $element.attr('data-panel'), 
					cache = {
						parent : $element,
						child : self.el.find('#' + id),
						position : defaults.position,
						open : false
					};
				_panels[id] = cache;
				self.panelToggle(id, cache.open);
			});
		},
		panelToggle : function(idOrTrigger, open) {
			var id = typeof idOrTrigger === 'string' ? idOrTrigger : idOrTrigger.attr('data-panel'),
				_panels = this._panels, cache = _panels[id], parent, child, icon,
				offset, css = {},
				_ICONS = APP._classes.icons;

			if (!cache) {
				throw 'APP.Panel ' + id + ' not defined or initialized';
			}
			parent = cache.parent, child = cache.child;
			open = typeof open !== 'undefined' ? open : !cache.open;
			icon = parent.children('.' + _ICONS.base);
			if (open) {
				offset = parent.offset();
				css.top = offset.top + parent.outerHeight();
				if (cache.position === 'right') {
					css.right = $(window).width() - offset.left - parent.outerWidth();
				} else {
					css.left = offset.left;
				}
				child.css(css);
			}
			child[open ? 'show' : 'hide']();
			if (icon.length) {
				icon.addClass(open ? _ICONS.triangle_n : _ICONS.triangle_s)
					.removeClass(!open ? _ICONS.triangle_n : _ICONS.triangle_s);
			}
			cache.open = open;
		}
	};

	APP.Tooltip = {
		tooltip : function(event) {
			var $tooltip = $('#tooltip'),
				$target = $(event.currentTarget),
				offset = $target.offset(),
				x = event.pageX, y = event.pageY;

			if ($tooltip.length === 0) {
				$tooltip = $('<div id="tooltip" style="position: absolute; z-index: 999999; "></div>').appendTo($('body'));
			}

			$tooltip.html(this.tooltipHtml($target))
				.css({ top : y + 2, left : x + 2 })
				.show();

			this._tooltip = {
				t : Math.ceil(offset.top),
				l : Math.ceil(offset.left),
				w : $target.outerWidth(),
				h : $target.outerHeight(),
				$el : $tooltip
			};

			$(document).bind('mousemove.tooltip', this.proxy(this.tooltipTrack));
		},
		tooltipTrack : function(event) {
			var _tooltip = this._tooltip,
				$el, x = event.pageX, y = event.pageY;

			if (!_tooltip) {
				return;
			}
			$el = _tooltip.$el;
			if (x < _tooltip.l || x > _tooltip.l + _tooltip.w || y < _tooltip.t || y > _tooltip.t + _tooltip.h) {
				$el.hide();
				$(document).unbind('.tooltip');
				this._tooltip = false;
			} else {
				$el.css({ top : y + 2, left : x + 2 });
			}
		}
	};
	// start Template module
	APP.Template = {
		tmpl : function tmpl(str, data){
			var _tmpl = this._tmpl || (this._tmpl = {}), 
				fn = !/\W/.test(str) ?
					_tmpl[str] = _tmpl[str] ||
					this.tmpl(document.getElementById(str).innerHTML) :
					new Function("obj", "var p=[],print=function(){p.push.apply(p,arguments);};" +
						// Introduce the data as local variables using with(){}
						"with(obj){p.push('" +
						// Convert the template into pure JavaScript
						str
						  .replace(/[\r\t\n]/g, " ")
						  .split("<%").join("\t")
						  .replace(/((^|%>)[^\t]*)'/g, "$1\r")
						  .replace(/\t=(.*?)%>/g, "',$1,'")
						  .split("\t").join("');")
						  .split("%>").join("p.push('")
						  .split("\r").join("\\'")
					  + "');}return p.join('');");
			// Provide some basic currying to the user
			return data ? fn( data ) : fn;
		},
		tmplAll : function(el, items, template) {
			var i, len = items && items.length;

			el.empty();
			if (len) {
				template = template || el.attr('data-template') || 'tmpl' + items[0].constructor.className;
				for (i = 0; i < len; i ++) {
					el.append(this.tmpl(template, items[i]));
				}
			}
		}
	};
	// start Queue module
	APP.Queue = {
		queue : function() {
			var args = Array.prototype.slice.call(arguments, 0),
				callback = args.shift(),
				caller = args.shift(),
				_queued = this._queued || (this._queued = []),
				func;

			if (this._queueResolved) {
				callback.apply(caller, args);
				return;
			}
			if (typeof callback === 'function') {
				func = {
					callback : callback,
					caller : caller,
					args : args
				}
				_queued.push(func);
			}
		},
		queueBind : function() {
			var _bound = this._bound || (this._bound = {}),
				args = Array.prototype.slice.call(arguments, 0),
				current;

			while (args.length) {
				current = args.shift();
				switch (typeof current) {
				case 'string' : 
					_bound[current] = false;
					break;
				case 'function' :
					current(this);
					break;
				}
			}
		},
		queueResolve : function(id) {
			var _bound = this._bound, _id;
			if (typeof _bound[id] === 'undefined') {
				throw 'function ' + id + ' has not been bound'
				return;
			}
			_bound[id] = true;
			for (_id in _bound) {
				if (!_bound[_id]) {
					return;
				}
			}
			this._queueResolved = true;
			this._queueFire();
		},
		_queueFire : function() {
			var _queued = this._queued, func;
			if (!_queued || _queued.length === 0) {
				return;
			}
			while (_queued.length > 0) {
				func = _queued.shift();
				func.callback.apply(func.caller, func.args);
			}
		}		
	};
	// start cache module
	APP.Cache = {
		cache : function(key, val) {
			var current = this._cache || (this._cache = {}), 
				parts = key.split('.'), _i, _len;

			for (_i = 0, _len = parts.length - 1; _i < _len; _i ++) {
				if (typeof current[parts[_i]] === 'undefined') {
					current[parts[_i]] = {}
				}
				current = current[parts[_i]];
			}
			if (typeof val === 'undefined') {
				return current[parts[_i]];
			}
			current[parts[_i]] = val;
			return val;
		}
	};
	// start MouseHandler module
	APP.MouseHandler = {
		highlight : function(event) {
			var $target = $(event.currentTarget),
				over = event.type === 'mouseenter',
				_STATES = APP._classes.states;

			if ($target.hasClass(_STATES.active) || $target.hasClass(_STATES.disabled)) {
				return;
			}
			$target[over ? 'addClass' : 'removeClass'](_STATES.hover);
			this.trigger('highlight', $target, event);
		},
		click : function(event) {
			var $target = $(event.currentTarget),
				_STATES = APP._classes.states;

			event.preventDefault();
			event.stopPropagation();

			$target.blur();
			if ($target.hasClass(_STATES.active) || $target.hasClass(_STATES.disabled)) {
				return;
			}
			$target.removeClass(_STATES.hover);
			this.trigger('click', $target, event);
		}
	};
	// start Message module
	APP.Message = {
		message : function(message, error) {
			var _STATES = APP._classes.states, $msg;

			if (!this._msgInit) {
				$msg = $('#flashMessage');
				if ($msg.length) {
					setTimeout(function() {
						$msg.fadeOut('slow');
					}, 2000);
				}
				this._msgInit = true;
			}

			if (!this.$message) {
				this.$message = this.el.find('.message');
				if (this.$message.length === 0) {
					this.$message = $('<div class="message"></div>').prependTo(this.el);
				}
			} else if (typeof this.$message === 'string') {
				this.$message = $(this.$message);
			}

			this.$message.empty()
				.removeClass(_STATES.highlight + ' ' + _STATES.error);

			if (message) {
				this.$message.text(message)
					.addClass(error ? _STATES.error : _STATES.highlight)
					.show();
			} else {
				this.$message.hide();
			}
		}
	}

	global.APP = APP;
	global.parseImage = function (upload, callback) {
		var element = document.createElement("img"),
			src, object;

		element.onerror = callback;
		element.onload = function () {
			object && parseImage.revokeObjectURL(object);
			callback(parseImage.canvas(element))
		};
		return (src = window.Blob && upload instanceof Blob || 
				window.File && upload instanceof File ? object = parseImage.createObjectURL(upload) : upload) ? 
				(element.src = src, element) : 
				parseImage.readFile(upload, function (upload) {
					element.src = upload
				});
	};
	var urlFunc = window.createObjectURL && window || window.URL && URL || window.webkitURL && webkitURL;

	global.parseImage.canvas = function (upload) {
		return upload;
		var canvas = document.createElement("canvas");
		if (!canvas.getContext) {
			return upload;
		}
		canvas.width = upload.width;
		canvas.height = upload.height;
		canvas.getContext("2d").drawImage(upload, 0, 0, upload.width, upload.height);
		return canvas;
	};

	global.parseImage.createObjectURL = function (a) {
		return urlFunc ? urlFunc.createObjectURL(a) : false;
	};

	global.parseImage.revokeObjectURL = function (a) {
		return urlFunc ? urlFunc.revokeObjectURL(a) : false;
	};

	global.parseImage.readFile = function (a, callback) {
		if (window.FileReader && FileReader.prototype.readAsDataURL) {
			var reader = new FileReader;
			reader.onload = function (file) {
				callback(file.target.result);
			};
			reader.readAsDataURL(a);
			return reader;
		}
		return false;
	};

	// GLOBAL prototype functions
	String.prototype.humanize = function(){
		// underscored or dot classed to Upper Case w/ spaces
		return this.replace(/[\._]/g, ' ').replace(/(^|\s)([a-z0-9])/g, function(m, p1, p2){ return p1 + p2.toUpperCase(); } );
	};
	String.prototype.classify = function() {
		// dot classed to CamelCase
		return this.replace(/(^|\.)([a-z0-9])/g, function(m, p1, p2){ return p2.toUpperCase(); } );
	};
	String.prototype.underscore = function() {
		// adds underscores and lowercases capital letters
		return this.replace(/[A-Z]/g, function(m, p1, p2) { return p1 ? '_' + m.toLowerCase() : m.toLowerCase(); });
	};
	String.prototype.tableize = function() {
		return this.pluralize().underscore();
	};
	String.prototype.pluralize = function() {
		// pluralizes a word
		var _ret = this.replace(/y$/, 'ies');
		if (!/s$/.test(_ret)) {
			_ret += 's';
		}
		return _ret;
	};
	String.prototype.$escape = function() {
		// jquery escape for selectors w/ dots in name
		return this.replace(/\./g, '\\.');
	};
	String.prototype.interpolate = function(lookup, flag) {
		var flag = flag || '%', parts = this.split(flag), 
			_i, _len, _cur, _ret;

		if (parts.length % 2 === 0) {
			throw 'globals.js::interpolate: incorrect number of %flags%';
		}
		for (_i = 1, _len = parts.length; _i < _len; _i += 2) {
			_cur = parts[_i];
			//if (!lookup[_cur]) {
			//	throw 'globals.js::interpolate: cannot find ' + _cur + ' in lookup.'
			//}
			parts[_i] = lookup[_cur] || '';
		}
		return parts.join('');
	};
} (this));